#lang plai-typed

(define-type ExprC
  [numC (n : number)]
  [idC (s : symbol)]
  [appC (func : ExprC) (arg : ExprC)]
  [plusC (l : ExprC) (r : ExprC)]
  [multC (l : ExprC) (r : ExprC)]
  [lamC (arg : symbol) (body : ExprC)]
  [fdC (name : symbol) (arg : symbol) (body : ExprC)])

;(fdC ’double ’x (plusC (idC ’x) (idC ’x)))
; DONE
; (interp (appC 'double (numC 5)) (list (fdC 'const5 '_ (numC 5)) (fdC 'double 'x (plusC (idC 'x) (idC 'x)))))


(define-type Binding
  [bind (name : symbol) (val : Value)])
(define-type-alias Env (listof Binding))
(define mt-env empty)
(define extend-env cons)
; TODO : Replace Substitution with Env
; 6.2 Interpriting with Enviroments
; TODO : Define lookup
(define (lookup [for : symbol] [env : Env]) : Value
  (cond
    [(empty? env) (error 'lookup "name not found")]
    [else (cond
            [(symbol=? for (bind-name (first env)))
             (bind-val (first env))]
            [else (lookup for (rest env))])]))

(define-type Value
  [numV (n : number)]
  [closV (arg : symbol) (body : ExprC) (env : Env)])

(define (num+ [l : Value] [r : Value]) : Value
    (cond
      [(and (numV? l) (numV? r))
       (numV (+ (numV-n l) (numV-n r)))]
      [else
       (error 'num+ "one argument was not a number")]))

(define (num* [l : Value] [r : Value]) : Value
    (cond
      [(and (numV? l) (numV? r))
       (numV (* (numV-n l) (numV-n r)))]
      [else
       (error 'num+ "one argument was not a number")]))

(define (interp [expr : ExprC] [env : Env]) : Value
  (type-case ExprC expr
    [numC (n) (numV n)]
    [idC (n) (lookup n env)]
    [plusC (l r) (num+ (interp l env) (interp r env))]
    [multC (l r) (num* (interp l env) (interp r env))]
    ; Bind (n,lamC(a,b)) to current env)
    [fdC (n a b) (closV a b (extend-env (bind n (closV a b env)) env))]
    [lamC (a b) (closV a b env)]
    [appC (f a) (local ([define f-value (interp f env)])
                  (interp (closV-body f-value)
                          ;<appC-interp-bind-in-env>
                          (extend-env (bind (closV-arg f-value)
                                            (interp a env))
                                      (closV-env f-value))))]
    
    ))

 (test (interp (plusC (numC 10) (appC (fdC 'const5 '_ (numC 5)) (numC 10)))
                mt-env)
       (numV 15))

; Error case in our implementation of Env 1st.
; Our test case equal to
; (define (f1 x) (f2 4))
; (define (f2 y) (+ x y))
; (f1 3)
; If we take a look, we will find error binding that x is not binded in f2's definition.
